﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NFit.Core.Errors;
using System.Reflection;
using NFit.Core.Interfaces;

namespace NFit.Core
{
    /// <summary>
    /// Execute a fixture
    /// </summary>
    public class DefaultFixtureExecution : IFixtureExecution
    {
        /// <summary>
        /// Indicate if the current specification as an implementation
        /// </summary>
        public bool IsATestFixture { get; set; }

        /// <summary>
        /// Run the fixture
        /// </summary>
        /// <returns>true if the fixture succeeded</returns>
        public virtual bool Run(IExecutionContext Context, SpecificationDefinition CurrentSpecification, FixtureDefinition FixtureDef)
        {
            IsATestFixture = CheckForTestFixture(Context, CurrentSpecification, FixtureDef);
            AbstractFixture rootfixture = GetRootFixture(Context, CurrentSpecification, FixtureDef, IsATestFixture);
            FixtureDef.Validate(IsATestFixture);

            if (!FixtureDef.Status.Success.HasValue)
                FixtureDef.Status.Success = RunFixtureRows(Context, CurrentSpecification, FixtureDef, rootfixture, IsATestFixture);

            return FixtureDef.Status.Success.Value;
        }

        private static bool CheckForTestFixture(IExecutionContext Context, SpecificationDefinition CurrentSpecification, FixtureDefinition FixtureDef)
        {
            if (CurrentSpecification.ClassType == null)
                return false;

            if (!string.IsNullOrEmpty(FixtureDef.ClassName))
                return false;

            if (FixtureDef.ClassType != null)
                return false;

            if (GetFixtureTypeFromFirstRow(Context, CurrentSpecification, FixtureDef) != null)
                return false;

            return true;
        }

        private static AbstractFixture GetRootFixture(IExecutionContext Context, SpecificationDefinition CurrentSpecification, FixtureDefinition FixtureDef, bool IsATestFixture)
        {
            AbstractFixture rootfixture;

            if (IsATestFixture)
                return null;

            if (FixtureDef.ClassType == null)
                rootfixture = ResolveFixtureClass(Context, CurrentSpecification, FixtureDef);
            else
                rootfixture = FixtureDef.GetImplementation();
            return rootfixture;
        }

        private static bool? RunFixtureRows(IExecutionContext Context, SpecificationDefinition CurrentSpecification, FixtureDefinition FixtureDef, AbstractFixture rootfixture, bool IsATestFixture)
        {
            bool res = true;
            if (IsATestFixture)
            {
                foreach (FixtureRow rw in FixtureDef.Rows)
                {
                    res = res & RunRow(rw, () => RunTestFixtureRow(Context, CurrentSpecification, FixtureDef, rw));
                }
            }
            else
            {
                foreach (FixtureRow rw in FixtureDef.Rows)
                {
                    res = res & RunFixtureRow(Context, CurrentSpecification, FixtureDef, rootfixture, rw);
                }
            }
            return res;
        }

        private static bool RunRow(FixtureRow rw, Func<bool> act)
        {
            try
            {
                return act();
            }
            catch (Exception ex)
            {
                rw.Status.NestedError = new UnexpectedError(ex);
                rw.Status.Success = false;
            }
            return false;
        }

        private static bool RunRowMethod(FixtureRow rw, string methodName, string target, Func<bool> act)
        {
            try
            {
                return act();
            }
            catch (MethodNotFoundException)
            {
                rw.Status.NestedError = ElementNotFoundError.Method(methodName, target);
                rw.Status.Success = false;
            }
            catch (Exception ex)
            {
                rw.Status.NestedError = new UnexpectedError(ex);
                rw.Status.Success = false;
            }
            return false;
        }

        private static bool RunTestFixtureRow(IExecutionContext Context, SpecificationDefinition CurrentSpecification, FixtureDefinition FixtureDef, FixtureRow rw)
        {
            if (ReflectionHelper.HasMethod(Context, CurrentSpecification.ClassType, rw[0].Text))
                return RunRowMethod(rw, rw[0].Text, CurrentSpecification.ClassType.FullName, () => RunFluentTestFixtureRow(Context, CurrentSpecification, FixtureDef, rw));

            IList<string> names = new List<string>();
            IList<string> args = new List<string>();
            for (int i = 0; i < rw.Count; i++)
            {
                if (i % 2 == 0)
                {
                    names.Add(rw[i].Text);
                }
                else
                {
                    args.Add(rw[i].Text);
                }
            }

            string conformed = Context.NameProvider.ElementName(names.ToArray());
            if (!ReflectionHelper.HasMethod(Context, CurrentSpecification.ClassType, conformed))
            {
                rw.Status.NestedError = ElementNotFoundError.Method(conformed, CurrentSpecification.ClassType.FullName);
                rw.Status.Success = false;
                return false;
            }

            bool res = RunRowMethod(rw, conformed, CurrentSpecification.Implementation.GetType().FullName, () => ReflectionHelper.RunMethod(Context, CurrentSpecification.Implementation, rw[0], rw[0], conformed, false, args.ToArray()));
            for (int i = 0; i < rw.Count; i++)
            {
                if (i % 2 == 0)
                {
                    rw[i].Status.Success = res;
                }
            }
            return res;
        }

        private static bool RunFluentTestFixtureRow(IExecutionContext Context, SpecificationDefinition CurrentSpecification, FixtureDefinition FixtureDef, FixtureRow rw)
        {
            object methodOwner = CurrentSpecification.Implementation;
            for (int i = 0; i < rw.Count; i++)
            {
                string conformed = Context.NameProvider.ElementName(rw[i].Text);
                string arg = null;
                try
                {
                    if (i < rw.Count - 1)
                    {
                        arg = rw[i + 1].Text;
                        methodOwner = ReflectionHelper.RunMethod(methodOwner, conformed, new string[] { arg });
                        rw[i].Status.Success = true;
                        i++;
                    }
                    else
                    {
                        if (methodOwner != null && ReflectionHelper.HasMethod(Context, methodOwner.GetType(), conformed))
                        {
                            methodOwner = ReflectionHelper.RunMethod(methodOwner, conformed, null);
                        }
                        rw[i].Status.Success = true;
                    }

                }
                catch (Exception)
                {
                    rw[i].Status.Success = false;
                    throw;
                }
            }
            return true;
        }

        private static bool RunFixtureRow(IExecutionContext Context, SpecificationDefinition CurrentSpecification, FixtureDefinition FixtureDef, AbstractFixture rootfixture, FixtureRow rw)
        {
            rootfixture.Context = Context;
            rootfixture.Specification = CurrentSpecification;
            rootfixture.FixtureDefinition = FixtureDef;
            rootfixture.Row = rw;
            //rw.Status.Success = true;
            bool res = RunRow(rw, () => rootfixture.Run());
            
            return res;
        }

        private static AbstractFixture ResolveFixtureClass(IExecutionContext Context, SpecificationDefinition CurrentSpecification, FixtureDefinition FixtureDef)
        {
            if (FixtureDef.ClassType == null && string.IsNullOrEmpty(FixtureDef.ClassName))
            {
                return ResolveFixtureClassFromFirstRow(Context, CurrentSpecification, FixtureDef);
            }

            if (FixtureDef.ClassType == null && !string.IsNullOrEmpty(FixtureDef.ClassName))
            {
                FixtureDef.ClassType = Context.Catalog.GetFixture(FixtureDef.ClassName);
                return FixtureDef.GetImplementation();
            }
            return null;
        }

        private static Type GetFixtureTypeFromFirstRow(IExecutionContext Context, SpecificationDefinition CurrentSpecification, FixtureDefinition FixtureDef)
        {
            string target = Context.NameProvider.ElementName(FixtureDef[0][0].Text);
            Type t = Context.Catalog.GetFixture(target);
            if (t != null && t.IsAFixtureImplementation())
            {
                return t;
            }

            return null;
        }

        private static AbstractFixture ResolveFixtureClassFromFirstRow(IExecutionContext Context, SpecificationDefinition CurrentSpecification, FixtureDefinition FixtureDef)
        {
            Type t = GetFixtureTypeFromFirstRow(Context, CurrentSpecification, FixtureDef);
            if (t != null)
            {
                FixtureDef.ClassName = t.Name;
                FixtureDef.ClassType = t;
                FixtureDef.Arguments = new FixtureRow(FixtureDef[0].GetArray(1));

                AbstractFixture fixture = FixtureDef.GetImplementation();
                fixture.Context = Context;
                fixture.FixtureDefinition = FixtureDef;
                fixture.Specification = CurrentSpecification;

                FixtureDef.Rows.RemoveAt(0); //Remove the first row that contains fixture class and arguments

                fixture.Resolve();
                return fixture;
            }

            return null;
        }
    }
}
